package com.google.code.UtopiaXml;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Helper class for CodeGeneration and name conversions. Lines of code are
 * inserted into it by appendLine() method and it ensures proper indent. Then it
 * can be converted to code by the toString() method. It also supports storing
 * imports that can be later retrieved by getImports()
 *
 * @author Michael Simacek
 */
public class CodeBuilder {

    private static class Line {

        public int indent;
        public String line;

        public Line(int indent, String line) {
            this.indent = indent;
            this.line = line;
        }
    }
    private int currentIndent;
    private List<Line> lines;
    private SortedSet<String> imports;
    public static final String NEWLINE = System.getProperty("line.separator");
    public static final String TAB = "   ";
    private static final Line emptyLine = new Line(0, "");

    /**
     * Creates empty CodeBuilder with no lines and no imports and zero indent.
     */
    public CodeBuilder() {
        lines = new ArrayList<>();
        imports = new TreeSet<>();
        currentIndent = 0;
    }

    /**
     * * Creates empty CodeBuilder with no lines and no imports and given
     * indent
     *
     * @param indent indent in number of tabs. If the content shoul be inserted
     * to a class, it should probably be 1.
     */
    public CodeBuilder(int indent) {
        lines = new ArrayList<>();
        imports = new TreeSet<>();
        currentIndent = indent;
    }

    /**
     * Adds import from given import name. If it's null, empty or already
     * present, it doesn't do anything.
     *
     * @param imp import name (for example "java.math.BigDecimal") or empty or
     * null.
     */
    public void addImport(String imp) {
        if (!"".equals(imp)) {
            imports.add(imp);
        }
    }

    /**
     * Returns the SortedSet of all import names.
     *
     * @return all import names
     */
    public SortedSet<String> getImports() {
        return imports;
    }

    /**
     * Appends line at the end of the CodeBuilder content. Proper indent is
     * ensured automatically.
     *
     * @param line String to be appended
     */
    public void appendLine(String line) {
        if (line.matches("^\\s*\\}.*")) {
            currentIndent--;
        }
        lines.add(new Line(currentIndent, line));
        if (line.matches(".*\\{\\s*$")) {
            currentIndent++;
        }
    }

    /**
     * Inserts a blank line at the end of the CodeBuilder content.
     */
    public void newLine() {
        lines.add(emptyLine);
    }

    /**
     * Returns resulting code as one big String. Imports are not included, they
     * have to be retrieved separately.
     *
     * @return Resulting generated String
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Line line : lines) {
            for (int i = 0; i < line.indent; i++) {
                sb.append(TAB);
            }
            sb.append(line.line).append(NEWLINE);
        }
        return sb.toString();
    }

    /**
     * Helper method for generating import name from fully qualified name
     *
     * @param typename qualified name
     * @return typename if it's valid import String, empty String otherwise
     */
    public static String getImportFromFullName(String typename) {
        String[] parts = typename.split("\\.");
        if (parts.length < 2) {
            return "";
        } else if (parts.length == 2 && parts[0].equals("java") && parts[1].equals("lang")) {
            return "";
        } else {
            return typename;
        }
    }

    /**
     * Helper metod for converting fully qualified name to unqualified
     *
     * @param typename qualified name
     * @return unqualified name
     */
    public static String getNameFromFullName(String typename) {
        String[] parts = typename.split("\\.");
        if (parts.length > 1) {
            return parts[parts.length - 1];
        } else {
            return typename;
        }
    }

    /**
     * Helper method for converting XSD names to lower camel case form. Example:
     * some-long_text -> someLongText. If the name is reserved java keyword, it
     * is appended with an underscore.
     *
     * @param str input string separated by spaces or hyphens or underscores
     * @return converted string
     */
    public static String toLowerCamelCase(String str) {
        String[] words = str.split("[-_:. ]");
        if (words.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        sb.append(words[0].substring(0, 1).toLowerCase());
        sb.append(words[0].substring(1));
        for (int i = 1; i < words.length; i++) {
            sb.append(words[i].substring(0, 1).toUpperCase());
            sb.append(words[i].substring(1));
        }
        String ret = sb.toString();
        if (isKeyword(ret)) {
            return ret + "_";
        } else {
            return ret;
        }
    }

    /**
     * Helper method for converting XSD names to upper camel case form Example:
     * some-long_text -> SomeLongText.
     *
     * @param str input string separated by spaces or hyphens or underscores
     * @return converted string
     */
    public static String toUpperCamelCase(String str) {
        String[] words = str.split("[-_:. ]");
        if (words.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (String word : words) {
            sb.append(word.substring(0, 1).toUpperCase());
            sb.append(word.substring(1));
        }
        String ret = sb.toString();
        if (str.endsWith("_")) {
            return ret + "_";
        } else {
            return ret;
        }
    }
    private static final Set<String> keywords = new HashSet<>();

    static {
        keywords.add("if");
        keywords.add("else");
        keywords.add("do");
        keywords.add("while");
        keywords.add("for");
        keywords.add("private");
        keywords.add("protected");
        keywords.add("public");
        keywords.add("class");
        keywords.add("enum");
        keywords.add("int");
        keywords.add("short");
        keywords.add("long");
        keywords.add("char");
        keywords.add("boolean");
        keywords.add("byte");
        keywords.add("void");
        keywords.add("switch");
        keywords.add("case");
        keywords.add("default");
        keywords.add("static");
        keywords.add("final");
        keywords.add("new");
        keywords.add("volatile");
        keywords.add("synchronized");
        keywords.add("try");
        keywords.add("catch");
        keywords.add("finally");
        keywords.add("continue");
        keywords.add("break");
        keywords.add("extends");
        keywords.add("implements");
        keywords.add("final");
        keywords.add("package");
        keywords.add("native");
        keywords.add("this");
        keywords.add("super");
        keywords.add("goto");
        keywords.add("throws");
        keywords.add("throw");
        keywords.add("if");
        keywords.add("import");
        keywords.add("instanceof");
        keywords.add("if");
        keywords.add("abstract");
        keywords.add("assert");
        keywords.add("const");
        keywords.add("if");
        keywords.add("double");
        keywords.add("float");
        keywords.add("if");
        keywords.add("transient");
        keywords.add("return");
        keywords.add("strictfp");
        keywords.add("interface");
    }

    private static boolean isKeyword(String str) {
        return keywords.contains(str);
    }
}
